#ifndef __TERRAIN_H__
#define __TERRAIN_H__

#include <cstdint>
#include "Engine/Vector2.hpp"
#include "Engine/Vector3.hpp"
#include "Engine/Point2.hpp"
#include "Engine/Point3.hpp"
#include "Engine/MathUtils.hpp"

using namespace PocketSurvival::Engine;

namespace PocketSurvival
{
    /*
                            (0xFFFFC000)              (0x3C00)       (0x3FF)
                +-------------- data ---------------+ +light+ +---- contents ----+
                |                                   | |     | |                  |
    BlockValue: 0 0 0 0 0 0 0 0  0 0 0 0 0 0 0 0  0 0 0 0 0 0 0 0  0 0 0 0 0 0 0 0      (32 bit)


                 (0xFF000000)                     (0xF000)              (0xFF)
               sun light height                   humidity            top height
                +-------------+                   +-----+          +-------------+
                |             |                   |     |          |             |
    ShaftValue: 0 0 0 0 0 0 0 0  0 0 0 0 0 0 0 0  0 0 0 0 0 0 0 0  0 0 0 0 0 0 0 0      (32 bit)
                                 |             |          |     |
                                 +-------------+          +-----+
                                  bottom height         temperature
                                    (0xFF0000)            (0xF00)
    */

    namespace Terrain
    {
        constexpr int32_t Size = 16;
        constexpr int32_t Height = 256;

        constexpr inline int32_t MakeBlockValue(int32_t contents)
        {
            return contents & 0x3FF;
        }
        constexpr inline int32_t MakeBlockValue(int32_t contents, int32_t light, int32_t data)
        {
            return (contents & 0x3FF) | ((light << 10) & 0x3C00) | ((data << 14) & 0xFFFFC000);
        }
        constexpr inline int32_t ExtractContents(int32_t blockValue)
        {
            return blockValue & 0x3FF;
        }
        constexpr inline int32_t ExtractLight(int32_t blockValue)
        {
            return (blockValue & 0x3C00) >> 10;
        }
        constexpr inline int32_t ExtractData(int32_t blockValue)
        {
            return (blockValue & 0xFFFFC000) >> 14;
        }
        constexpr inline int32_t ReplaceContents(int32_t blockValue, int32_t contents)
        {
            return blockValue ^ ((blockValue ^ contents) & 0x3FF);
        }
        constexpr inline int32_t ReplaceLight(int32_t blockValue, int32_t light)
        {
            return blockValue ^ ((blockValue ^ (light << 10)) & 0x3C00);
        }
        constexpr inline int32_t ReplaceData(int32_t blockValue, int32_t data)
        {
            return blockValue ^ ((blockValue ^ (data << 14)) & 0xFFFFC000);
        }

        constexpr inline int32_t ExtractTopHeight(int32_t shaftValue)
        {
            return shaftValue & 0xFF;
        }
        constexpr inline int32_t ExtractBottomHeight(int32_t shaftValue)
        {
            return (shaftValue & 0xFF0000) >> 16;
        }
        constexpr inline int32_t ExtractSunlightHeight(int32_t shaftValue)
        {
            return (shaftValue & 0xFF000000) >> 24;
        }
        constexpr inline int32_t ExtractHumidity(int32_t shaftValue)
        {
            return (shaftValue & 0xF000) >> 12;
        }
        constexpr inline int32_t ExtractTemperature(int32_t shaftValue)
        {
            return (shaftValue & 0xF00) >> 8;
        }
        constexpr inline int32_t ReplaceTopHeight(int32_t shaftValue, int32_t topHeight)
        {
            return shaftValue ^ ((shaftValue ^ topHeight) & 0xFF);
        }
        constexpr inline int32_t ReplaceBottomHeight(int32_t shaftValue, int32_t bottomHeight)
        {
            return shaftValue ^ ((shaftValue ^ (bottomHeight << 16)) & 0xFF0000);
        }
        constexpr inline int32_t ReplaceSunlightHeight(int32_t shaftValue, int32_t sunlightHeight)
        {
            return shaftValue ^ ((shaftValue ^ (sunlightHeight << 24)) & 0xFF000000);
        }
        constexpr inline int32_t ReplaceHumidity(int32_t shaftValue, int32_t humidity)
        {
            return shaftValue ^ ((shaftValue ^ (humidity << 12)) & 0xF000);
        }
        constexpr inline int32_t ReplaceTemperature(int32_t shaftValue, int32_t temperature)
        {
            return shaftValue ^ ((shaftValue ^ (temperature << 8)) & 0xF00);
        }

        constexpr inline int32_t ToCell(float x)
        {
            return (int32_t)MathUtils::Floor<float>(x);
        }
        constexpr inline Point3 ToCell(float x, float y, float z)
        {
            return {ToCell(x), ToCell(y), ToCell(z)};
        }
        constexpr inline Point3 ToCell(const Vector3 &p)
        {
            return {ToCell(p.posX), ToCell(p.posY), ToCell(p.posZ)};
        }
        constexpr inline Point2 ToChunk(int32_t x, int32_t z)
        {
            return {x >> 4, z >> 4};
        }
        constexpr inline Point2 ToChunk(const Vector2 &p)
        {
            return ToChunk(ToCell(p.posX), ToCell(p.posY));
        }
    }
}

#endif